<!doctype html>
<html>
<head>
    <title>Test Page</title>
    <style type="text/css">
        body { font: normal 87%/1.4 Arial, sans-serif; }
        input { width: 35em; }
        div input { width: auto; }
        xp { margin: 1.4em 0 1ex; }
    </style>
</head>
<body class="yui-skin-sam">
    <h1>The typing-pause special event</h1>
    <p>
        <label for="in">Type here and look for responses in the console</label>
    </p>
    <input id="in">

    <div>
        <p>
            <input type="radio" name="mode" id="mode1" value="1" checked="checked">
            <label for="mode1">Simple default subscription</label>
        </p>
        <p>
            <input type="radio" name="mode" id="mode2" value="2">
            <label for="mode2">Non-adaptive subscription (400ms delay)</label>
        </p>
        <p>
            <input type="radio" name="mode" id="mode3" value="3">
            <label for="mode3">Configured sub, fires after mention of &quot;dog&quot;</label>
        </p>

        <button type="button" id="attach">attach</button>
        <button type="button" id="attach_handle">attach + handle</button>
        <button type="button" id="attach_cat">attach + category</button>
        <button type="button" id="detach">detach by signature</button>
        <button type="button" id="detach_handle">detach by handle</button>
        <button type="button" id="detach_cat">detach by category</button>
        <button type="button" id="detach_nofn">detach without fn</button>
        <button type="button" id="remove">remove buttons</button>
    </div>
    <script src="../../../../build/yui/yui.js"></script>
    <script src="../../../../build/event/event-synthetic.js"></script>
    <script>
YUI().use('node-base', 'event-synthetic', function (Y) {

    function asIs(x) { return x; }

    Y.Event.define('typing-pause', {
        processArgs: function (args) {
            // Extract configuration from third position in the args
            // Apply defaults to the returned config object.
            return Y.mix({
                adaptive: true,
                minLength: 1,
                minWait: 400,
                maxWait: 3000,
                waitMultiplier: 4,
                filter: asIs,
            }, (args.splice(3,1)[0] || {}), true);
        },

        on: function (node, sub, ce) {
            sub._cat     = Y.guid();
            sub._strokes = 0;
            sub._first   = 0;
            sub._args    = Y.Array(arguments, 4, true);

            node.on(sub._cat+'|keyup', this._handleKey, null, sub, ce);
            node.on(sub._cat+'|blur', this._handleBlur, null, sub, ce);
        },

        detach: function (node, sub, ce) {
            node.detach(sub._cat + '|*');
            clearTimeout(sub._timer);
        },

        // Support functions
        _handleKey: function (e, sub, ce) {
            // Allow delete and backspace, but not shift, alt, ctrl, etc
            if (e.keyCode < 46 && e.keyCode !== 8 && e.keyCode !== 32) {
                return;
            }

            var raw       = this.get('value'),
                config    = sub._extra,
                value     = config.filter.call(this, raw),
                minLength = config.minLength,
                minWait   = config.minWait,
                maxWait   = config.maxWait,

                delay, now;

            if (sub._timer) {
                sub._timer.cancel();
                delete sub._timer;
            }

            if (value && value.length > minLength) {
                if (config.adaptive) {
                    now = new Date();

                    ++sub._strokes;

                    // On first key stroke, use maxWait because we don't
                    // have enough data to calculate a reasonable delay
                    if (sub._strokes === 1) {
                        sub._first = now;
                        delay = maxWait;
                    } else {
                        // @TODO: intentional pauses should not affect
                        // the average (e.g. fast typist that pauses
                        // enough to trigger the event, then leaves
                        // focus in the input.  The avg is then
                        // skewed.)
                        delay = (now - sub._first) / sub._strokes;
                        delay = Math.round(delay) * config.waitMultiplier;

                        if (delay > maxWait) {
                            delay = maxWait;
                        }
                    }

                    if (delay < minWait) {
                        delay = minWait;
                    }
                } else {
                    delay = minWait;
                }

                // Schedule firing according to the appropriate delay
                sub._timer = Y.later(delay, this, function () {
                    ce.fire({
                        inputValue: raw,
                        value: value,
                        lastKeyEvent: e,
                        target: this,
                        currentTarget: this
                    });
                });
            }
        },

        _handleBlur: function (e, sub, ce) {
            sub._strokes = 0;

            if (sub._timer) {
                sub._timer.cancel();
                delete sub._timer;
            }
        }
    });


    /****************************************************************/
    /********************** Test implementation *********************/
    /****************************************************************/
    var handle, last;

    function notify(e) {
        console.log(this, e, new Date() - last);
    }
    
    Y.one('#in').on('keyup', function (e) {
        last = new Date();
    });

    Y.one('#attach').on('click', function (e) {
        var mode = parseInt(Y.one("input[name=mode]:checked").get('value'));

        switch (mode) {
            case 1: Y.one('#in').on('typing-pause', notify); break;
            case 2: Y.one('#in').on('typing-pause', notify, {
                adaptive: false
            }); break;
            case 3: Y.one('#in').on('typing-pause', notify, {
                //adaptive: true,
                minLength: 8,
                minWait: 1000,
                maxWait: 5000,
                waitMultiplier: 2,
                filter: function (v) {
                    var m = /\bdog\b/i.exec(v);
                    return m ? ("'"+m[0]+"' at "+RegExp.leftContext.length) :'';
                }
            });
        }
    });

    Y.one('#attach_handle').on('click', function (e) {
        var mode = parseInt(Y.one("input[name=mode]:checked").get('value'));

        switch(mode) {
            case 1: handle = Y.one('#in').on('typing-pause', notify); break;
            case 2: handle = Y.one('#in').on('typing-pause', notify, {
                adaptive: false
            }); break;
            case 3: handle = Y.one('#in').on('typing-pause', notify, {
                minLength: 8,
                minWait: 1000,
                maxWait: 5000,
                waitMultiplier: 2,
                filter: function (v) {
                    var m = /\bdog\b/i.exec(v);
                    return m ? ("'"+m[0]+"' at "+RegExp.leftContext.length) :'';
                }
            });
        }
    });

    Y.one('#attach_cat').on('click', function (e) {
        var mode = parseInt(Y.one("input[name=mode]:checked").get('value'));

        switch (mode) {
            case 1: Y.one('#in').on('foo|typing-pause', notify); break;
            case 2: Y.one('#in').on('foo|typing-pause', notify, {
                adaptive: false
            }); break;
            case 3: Y.one('#in').on('foo|typing-pause', notify, {
                minLength: 8,
                minWait: 1000,
                maxWait: 5000,
                waitMultiplier: 2,
                filter: function (v) {
                    var m = /\bdog\b/i.exec(v);
                    return m ? ("'"+m[0]+"' at "+RegExp.leftContext.length) :'';
                }
            });
        }
    });

    Y.one('#detach').on('click', function (e) {
        var mode = parseInt(Y.one("input[name=mode]:checked").get('value'));

        switch (mode) {
            case 1: Y.one('#in').detach('typing-pause', notify); break;
            case 2: Y.all('#in').detach('typing-pause', notify); break;
            case 3: Y.detach('typing-pause', notify, '#in');
        }
    });

    Y.one('#detach_handle').on('click', function (e) {
        handle.detach();
    });

    Y.one('#detach_cat').on('click', function (e) {
        var mode = parseInt(Y.one("input[name=mode]:checked").get('value'));

        switch (mode) {
            case 1: Y.one('#in').detach('foo|typing-pause'); break;
            case 2: Y.all('#in').detach('foo|typing-pause'); break;
            case 3: Y.detach('foo|typing-pause');
        }
    });

    Y.one('#detach_nofn').on('click', function (e) {
        var mode = parseInt(Y.one("input[name=mode]:checked").get('value'));

        switch (mode) {
            case 1: Y.one('#in').detach('typing-pause'); break;
            case 2: all('#in').detach('typing-pause'); break;
            case 3: Y.detach('foo|*');
        }
    });

    Y.one('#remove').on('click', function (e) {
        inner.remove(true);
        Y.log('Detached');
    });

});
</script>
</body>
</html>
